/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <baja/common.hpp>
#include <baja/graphics.hpp>

#include <baja/graphics/scenes/scenes_fwd.hpp>
#include <baja/graphics/scenes/scenes_interfaces.hpp>
#include <baja/graphics/scenes/details/scene_node_base.hpp>

#include <amp.h>

namespace journal { namespace view {

namespace b = baja;
namespace g = baja::graphics;
namespace d = ::baja::graphics::scenes::details;
namespace c = concurrency;

#define FAST_SQRT(x, res) \
{ \
    union { int i; float x; } u; \
    u.x = x;  \
    u.i = (1<<29) + (u.i >> 1) - (1<<22); \
    u.x = 0.5f * (u.x + x/u.x); \
    res = u.x; \
}

#define LENGTH(x, y, z, res) \
{ \
    float t = x * x + y * y + z * z; \
    FAST_SQRT(t, res); \
}

#define NORMALIZE(x, y, z) \
{ \
    float len; \
    LENGTH((x), (y), (z), len); \
    float lenInv = (len != 0.f) ? 1.f / len : 0.f; \
    (x) *= lenInv; \
    (y) *= lenInv; \
    (z) *= lenInv; \
}

class paper_sheet_node : public d::scene_node_base<gs::iscene_node>, public std::enable_shared_from_this<paper_sheet_node>
{
public:
    paper_sheet_node() : _curling(false), _ampIndexArray(nullptr), _ampVertexNormalArray(nullptr), _ampInit(false) {}
    static std::shared_ptr<paper_sheet_node> create(
        const std::shared_ptr<g::iengine> graphics,
        const uint32 vertexCountX,
        const uint32 vertexCountY,
        const float32 offset
        )
    {
        std::shared_ptr<paper_sheet_node> returnValue = std::make_shared<paper_sheet_node>();
        returnValue->init(graphics, vertexCountX, vertexCountY, offset);
        return returnValue;
    }
    
    ~paper_sheet_node()
    {
        if (_ampIndexArray)
        {
            delete _ampIndexArray;
            _ampIndexArray = nullptr;
        }

        if (_ampVertexNormalArray)
        {
            delete _ampVertexNormalArray;
            _ampVertexNormalArray = nullptr;
        }
    }

    // gets the material
    std::shared_ptr<g::imaterial> getMaterial()
    {
        return _materials->getMaterial(0);
    }
    
    // gets the material collection
    std::shared_ptr<g::imaterial_collection> getMaterialCollection()
    {
        return _materials;
    }
    
    // gets the texture
    std::shared_ptr<g::itexture> getTexture()
    {
        std::shared_ptr<g::imaterial> material = this->getMaterial();
        return material->getTexture();
    }
    
    // gets the specified texture
    std::shared_ptr<g::itexture> getTextureAtIndex(uint32 index)
    {
        std::shared_ptr<g::imaterial> material;
        material = this->getMaterial();
        return material->getTextureAtIndex(index);
    }
    
    // sets the texture
    void setTexture(const std::shared_ptr<g::itexture>& texture)
    {
        std::shared_ptr<g::imaterial> material;
        material = this->getMaterial();

        material->setTexture(texture);
    }

    // sets the texture at a given index
    void setTextureAtIndex(uint32 index, const std::shared_ptr<g::itexture>& texture)
    {
        std::shared_ptr<g::imaterial> material;
        material = this->getMaterial();

        material->setTextureAtIndex(index, texture);
    }

    std::shared_ptr<g::ivertex_buffer> vertexBuffer()
    {
        return _mesh->vertexBuffer();
    }

    int vertexCount() { return _vertexCountX * _vertexCountY; }
    b::uint32 vertexCountX() { return _vertexCountX; }
    b::uint32 vertexCountY() { return _vertexCountY; }
    b::float32 width() { return _vertexCountX * _offset; }
    b::float32 height() { return _vertexCountY * _offset; }
    b::float32 offset() { return _offset; }
    b::uint32 triangleCount() { return 2 * (_vertexCountX - 1) * (_vertexCountY - 1); }

    // helper function to find access to a vertex given its coordinates in the mesh
    g::vertex_lit_colored_textured* getVertex(g::vertex_lit_colored_textured* buffer, const uint32 x, const uint32 y)
    {
        return &(buffer[y * _vertexCountX + x]);
    }

    // initializes the mesh
    void init(
        const std::shared_ptr<g::iengine>& graphics,
        const uint32 vertexCountX,
        const uint32 vertexCountY,
        const float32 offset
        )
    {
        // forget about any current mesh & material we were using
        _mesh.reset(); // TEST:
        _materials.reset();

        // init members
        _vertexCountX = vertexCountX;
        _vertexCountY = vertexCountY;
        _offset = offset;

        this->reset();
    }

    void reset()
    {
        b::int32 vertexCount = _vertexCountX * _vertexCountY;
        b::int32 indexCount = triangleCount() * 3;

        _vertexBuffer.resize(vertexCount);
        _indexBuffer.resize(indexCount);

        // Initialize vertices
        float32 x;
        float32 y;
        float32 u;
        float32 v;
        float32 uOffset = 1.0f / (_vertexCountX - 1);
        float32 vOffset = 1.0f / (_vertexCountY - 1);

        y = (_offset * (_vertexCountY - 1)) / 2.0f;
        v = 0;
        for (uint32 iy = 0; iy < _vertexCountY; iy++)
        {
            x = (-1.0f * _offset * (_vertexCountX - 1)) / 2.0f;
            u = 0;
            
            for (uint32 ix = 0; ix < _vertexCountX; ix++)
            {
                uint32 index = ix + iy * _vertexCountX;

                g::vertex_lit_colored_textured* vertex = &(_vertexBuffer[index]);
                
                vertex->position = m::point3d(x, y, 0.f);
                vertex->normal = m::point3d(0, 0, 1.0f);
                vertex->uv[0].u = u;
                vertex->uv[0].v = v;
                vertex->color = g::packColor(255, 255, 255, 255); // Default color

                x += _offset;
                u += uOffset;
            }

            y -= _offset;
            v += vOffset;
        }
        
        // Initialize indices
        // These three control the way the mesh is generated
        // backSlash = true means the first diagonal edge (top-left) is going to be going top-left to bottom-right "\"
        const bool alternateY = true;
        const bool alternateX = true;
        bool backSlash = true;
        int ii = 0;

        uint16 indices[6];

        for (uint32 iy = 0; iy < _vertexCountY - 1; iy++)
        {
            for (uint32 ix = 0; ix < _vertexCountX - 1; ix++)
            {
                // Clock-wise from top-left vertex:
                uint16 i1 = (iy * _vertexCountX + ix);
                uint16 i2 = i1 + 1; 
                uint16 i3 = i2 + _vertexCountX;
                uint16 i4 = i1 + _vertexCountX;

                    // We set the indices clock wise 
                if (backSlash)
                {
                    setIndices(indices, i1, i2, i3, i3, i4, i1);
                }
                else
                {
                    setIndices(indices, i4, i1, i2, i4, i2, i3);
                }
 
                int i = 0;
                while (i < 6)
                {
                    _indexBuffer[ii++] = indices[i++];
                }

                // Note edges added in counter-clockwise order
                // reverse the order of the diagonal link every other quad
                if (alternateX)
                {
                    backSlash = !backSlash;
                }
            }

            if (alternateY)
            {
                backSlash = !backSlash;
            }
        }

        // create the mesh
        _mesh = createMesh(
            constants::graphics(),
            _vertexBuffer.data(),
            vertexCount,
            _indexBuffer.data(),
            indexCount,
            g::primitive_type::triangleList,
            triangleCount()
            );

        // clone material collection
        _materials = _mesh->cloneMaterialCollection();

        this->setNodeDirty();
    }

    inline void ensureAmpInitialized()
    {
        if (_ampInit) return;

        c::accelerator_view cpuAv = c::accelerator(c::accelerator::cpu_accelerator).default_view;
        c::accelerator_view defaultAv = c::accelerator().default_view;
        c::array<c::index<1>, 2> ampIndexStagingArr(3, (int)triangleCount(), cpuAv, defaultAv);
        for (b::uint32 i = 0; i < triangleCount(); i++)
        {
            ampIndexStagingArr(0, i) = c::index<1>(_indexBuffer[i*3]);
            ampIndexStagingArr(1, i) = c::index<1>(_indexBuffer[(i*3) + 1]);
            ampIndexStagingArr(2, i) = c::index<1>(_indexBuffer[(i*3) + 2]);
        }
        _ampIndexArray = new c::array_view<const c::index<1>, 2>(ampIndexStagingArr);

        // Setup the AMP vertex normal array
        c::array<b::float32, 2> vertexNormalStagingArr(3, vertexCount(), cpuAv, defaultAv);

        for (int i = 0; i < vertexCount(); ++i) {
            vertexNormalStagingArr(0, i) = _vertexBuffer[i].normal.x;
            vertexNormalStagingArr(1, i) = _vertexBuffer[i].normal.y;
            vertexNormalStagingArr(2, i) = _vertexBuffer[i].normal.z;
        }

        _ampVertexNormalArray = new c::array_view<b::float32, 2>(vertexNormalStagingArr);

        _ampInit = true;
    }

    inline void setIndices(
        uint16* indices,
        uint16 i1,
        uint16 i2,
        uint16 i3,
        uint16 i4,
        uint16 i5,
        uint16 i6
        )
    {

        indices[0] = i1;
        indices[1] = i2;
        indices[2] = i3;
        indices[3] = i4;
        indices[4] = i5;
        indices[5] = i6;
    }

    void setCurling(const bool value)
    {
        _curling = value;
    }

    bool curling()
    {
        return _curling;
    }
    
    void updateNormals()
    {
        b::stopwatch sw;
    
        if (constants::performance::useAmp())
        {
            this->updateNormalsAmp();
        }
        else
        {
            this->updateNormalsCpu();
        }

        constants::performanceService()->collect(performance_event::normalCalculation, sw.getElapsedMilliseconds()); 
    }
    
    void updateNormalsCpu()
    {
        std::shared_ptr<g::iindex_buffer> indexBuffer = _mesh->indexBuffer();
        std::shared_ptr<g::ivertex_buffer> vertexBuffer = _mesh->vertexBuffer();

        g::vertex_lit_colored_textured* vertex;
        b::uint16* index;
        indexBuffer->lockNoOverwrite((b::memory**)&index, 0, triangleCount() * 3);
        vertexBuffer->lockNoOverwrite(0, _vertexCountX * _vertexCountY, (b::memory**)&vertex);

        std::vector<std::vector<m::vector3d>> normals;
        normals.resize(_vertexCountX * _vertexCountY);

        for (uint32 i = 0; i < triangleCount(); i++)
        {
            uint32 i1 = index[i * 3 + 2];
            uint32 i2 = index[i * 3 + 1];
            uint32 i3 = index[i * 3];

            m::point3d* p1 = &(vertex[i1].position);
            m::point3d* p2 = &(vertex[i2].position);
            m::point3d* p3 = &(vertex[i3].position);

            m::vector3d v1(*p2);
            v1 -= m::vector3d(*p1);

            m::vector3d v2(*p3);
            v2 -= m::vector3d(*p1);

            m::vector3d normal;
            v1.cross(v2, &normal);
            normal.normalize();

            vertex[i1].normal += normal;
            vertex[i2].normal += normal;
            vertex[i3].normal += normal;
        }

        for (uint32 i = 0; i < _vertexCountX * _vertexCountY; i++)
        {
            NORMALIZE(vertex[i].normal.x, vertex[i].normal.y, vertex[i].normal.z);
        }

        vertexBuffer->unlock();
        indexBuffer->unlock();
    }

    void updateNormalsAmp()
    {
        this->ensureAmpInitialized();

        // Calculate triangle normals
        std::shared_ptr<g::ivertex_buffer> vertexBuffer = _mesh->vertexBuffer();

        b::int32 indexCount = triangleCount() * 3;

        // lock the vertex buffer
        g::vertex_lit_colored_textured* vertexT;
        vertexBuffer->lockNoOverwrite(0, vertexCount(), (b::memory**)&vertexT);

        // Create AMP staging buffer and copy the vertex position data into it
        // to be used in the AMP parallel_for_each
        c::accelerator_view cpuAv = c::accelerator(c::accelerator::cpu_accelerator).default_view;
        c::accelerator_view defaultAv = c::accelerator().default_view;
        c::array<b::float32, 2> vertexPositionStagingArr(3, vertexCount(), cpuAv, defaultAv);
        for (int i = 0; i < vertexCount(); ++i) {
            vertexPositionStagingArr(0, i) = vertexT[i].position.x;
            vertexPositionStagingArr(1, i) = vertexT[i].position.y;
            vertexPositionStagingArr(2, i) = vertexT[i].position.z;
        }

        vertexBuffer->unlock();

        c::array_view<b::float32, 2> vertexPositionArray(vertexPositionStagingArr);
        c::array_view<const c::index<1>, 2> indexArray(*_ampIndexArray);

        // first calculate the triangle normals
        c::array<b::float32, 2> triangleNormals(3, (int)triangleCount());

        c::parallel_for_each(
            c::extent<1>(triangleCount()),
            [=, &triangleNormals](c::index<1> idx) restrict(amp)
        {
            b::float32 v1PosX = vertexPositionArray(0, indexArray(2, idx[0])[0]);
            b::float32 v1PosY = vertexPositionArray(1, indexArray(2, idx[0])[0]);
            b::float32 v1PosZ = vertexPositionArray(2, indexArray(2, idx[0])[0]);

            b::float32 v2PosX = vertexPositionArray(0, indexArray(1, idx[0])[0]);
            b::float32 v2PosY = vertexPositionArray(1, indexArray(1, idx[0])[0]);
            b::float32 v2PosZ = vertexPositionArray(2, indexArray(1, idx[0])[0]);

            b::float32 v3PosX = vertexPositionArray(0, indexArray(0, idx[0])[0]);
            b::float32 v3PosY = vertexPositionArray(1, indexArray(0, idx[0])[0]);
            b::float32 v3PosZ = vertexPositionArray(2, indexArray(0, idx[0])[0]);

            b::float32 x1 = v2PosX - v1PosX; 
            b::float32 y1 = v2PosY - v1PosY; 
            b::float32 z1 = v2PosZ - v1PosZ; 

            b::float32 x2 = v3PosX - v1PosX; 
            b::float32 y2 = v3PosY - v1PosY; 
            b::float32 z2 = v3PosZ - v1PosZ; 

            // cross them
            b::float32 x3 = y1 * z2 - z1 * y2;
            b::float32 y3 = z1 * x2 - x1 * z2;
            b::float32 z3 = x1 * y2 - y1 * x2;

            NORMALIZE(x3, y3, z3);

            triangleNormals(0, idx[0]) = x3;
            triangleNormals(1, idx[0]) = y3;
            triangleNormals(2, idx[0]) = z3;
        });

        // Now for each vector accumlate the normal from relevant triangles
        c::array_view<b::float32, 2> vertexNormalArray(*_ampVertexNormalArray);
        const c::array<b::float32, 2> &prevTriangleNormals = triangleNormals;

        int32 vertexCountX = this->vertexCountX();
        int32 vertexCountY = this->vertexCountY();
        c::parallel_for_each(
            c::extent<2>(vertexCountY, vertexCountX),
            [=, &prevTriangleNormals](c::index<2> idx) restrict(amp)
        {
            //m::vector3d &currVextexNormal = vertexNormalArray((idx[0] * vertexCountX) + idx[1]);

            b::float32 normalX = vertexNormalArray(0, (idx[0] * vertexCountX) + idx[1]);
            b::float32 normalY = vertexNormalArray(1, (idx[0] * vertexCountX) + idx[1]);
            b::float32 normalZ = vertexNormalArray(2, (idx[0] * vertexCountX) + idx[1]);

            // First look at the quad in which this vertex is the top-left node
            // This quad exists for all but the last row and column
            if ((idx[0] < (vertexCountY - 1)) && (idx[1] < (vertexCountX - 1)))
            {
                unsigned int firstTriangleIndex = 2 * ((idx[0] * (vertexCountX - 1)) + idx[1]);
                normalX += prevTriangleNormals(0, firstTriangleIndex);
                normalY += prevTriangleNormals(1, firstTriangleIndex);
                normalZ += prevTriangleNormals(2, firstTriangleIndex);

                // If this is a backslash then both triangles in the quad contribute 
                // to the vertex's normal; otherwise only the first one does
                bool backSlash = (((idx[0] + idx[1]) % 2) == 0);
                if (backSlash) {
                    normalX += prevTriangleNormals(0, firstTriangleIndex + 1);
                    normalY += prevTriangleNormals(1, firstTriangleIndex + 1);
                    normalZ += prevTriangleNormals(2, firstTriangleIndex + 1);
                }
            }

            // Now look at the quad originating from the left neighbor
            // This quad exists for all but the vertices in the leftmost column
            if (idx[1] != 0) 
            {
                unsigned int firstTriangleIndex = 2 * ((idx[0] * (vertexCountX - 1)) + idx[1] - 1);
                normalX += prevTriangleNormals(0, firstTriangleIndex);
                normalY += prevTriangleNormals(1, firstTriangleIndex);
                normalZ += prevTriangleNormals(2, firstTriangleIndex);

                // If this is not a backslash then both triangles in the quad contribute 
                // to the vertex's normal; otherwise only the first one does
                bool backSlash = (((idx[0] + idx[1] - 1) % 2) == 0);
                if (!backSlash) {
                    normalX += prevTriangleNormals(0, firstTriangleIndex + 1);
                    normalY += prevTriangleNormals(1, firstTriangleIndex + 1);
                    normalZ += prevTriangleNormals(2, firstTriangleIndex + 1);
                }
            }

            // Now look at the quad originating from the top neighbor
            // This quad exists for all but the vertices in the topmost row
            if (idx[0] != 0) 
            {
                unsigned int firstTriangleIndex = 2 * (((idx[0] - 1) * (vertexCountX - 1)) + idx[1]);
                normalX += prevTriangleNormals(0, firstTriangleIndex + 1);
                normalY += prevTriangleNormals(1, firstTriangleIndex + 1);
                normalZ += prevTriangleNormals(2, firstTriangleIndex + 1);

                // If this is not a backslash then both triangles in the quad contribute 
                // to the vertex's normal; otherwise only the first one does
                bool backSlash = (((idx[0] + idx[1] - 1) % 2) == 0);
                if (!backSlash) {
                    normalX += prevTriangleNormals(0, firstTriangleIndex);
                    normalY += prevTriangleNormals(1, firstTriangleIndex);
                    normalZ += prevTriangleNormals(2, firstTriangleIndex);
                }
            }

            // Now look at the quad originating from the top-left neighbor
            // This quad exists for all but the vertices in the first row or column
            if ((idx[0] != 0) && (idx[1] != 0))
            {
                unsigned int firstTriangleIndex = 2 * (((idx[0] - 1) * (vertexCountX - 1)) + idx[1] - 1);
                normalX += prevTriangleNormals(0, firstTriangleIndex + 1);
                normalY += prevTriangleNormals(1, firstTriangleIndex + 1);
                normalZ += prevTriangleNormals(2, firstTriangleIndex + 1);

                // If this is a backslash then both triangles in the quad contribute 
                // to the vertex's normal; otherwise only the first one does
                bool backSlash = (((idx[0] + idx[1] - 2) % 2) == 0);
                if (backSlash) {
                    normalX += prevTriangleNormals(0, firstTriangleIndex);
                    normalY += prevTriangleNormals(1, firstTriangleIndex);
                    normalZ += prevTriangleNormals(2, firstTriangleIndex);
                }
            }

            NORMALIZE(normalX, normalY, normalZ);

            vertexNormalArray(0, (idx[0] * vertexCountX) + idx[1]) = normalX;
            vertexNormalArray(1, (idx[0] * vertexCountX) + idx[1]) = normalY;
            vertexNormalArray(2, (idx[0] * vertexCountX) + idx[1]) = normalZ;
        });

        vertexNormalArray.synchronize();

        vertexBuffer->lockNoOverwrite(0, vertexCount(), (b::memory**)&vertexT);

        b::float32 *updatedVertexNormals = &vertexNormalArray(0, 0);
        for (int i = 0; i < vertexCount(); i++)
        {
            vertexT[i].normal.x = updatedVertexNormals[i];
            vertexT[i].normal.y = updatedVertexNormals[vertexCount() + i];
            vertexT[i].normal.z = updatedVertexNormals[(2 * vertexCount()) + i];
        }

        vertexBuffer->unlock();
    }

    void renderInternal( 
        const std::shared_ptr<g::iengine>& graphics,
        const m::matrix44* localToWorld
        )
    {
        bool pushedMatrix = false;

        if (localToWorld == nullptr)
        {
            // we are using matrix stack to properly transform matrices
            m::matrix44 mat;
            this->getLocalToParent(&mat);
            graphics->pushWorldMatrix(mat, true);
            pushedMatrix = true;
        }

        graphics->setAlphaBlend(g::enabled_state::enabled);
        graphics->setCulling(g::cull_state::none);

        // we are doing the curling rendering in two passes:
        // first pass we render the forward facing triangles (front of the page)
        // second pass we render the back facing triangles (on top of the forward facing ones)
        // if we don't do it this way we will see some funny rendering artifacts (zebra stripes) because the rows of the page
        // render top to bottom and hence:
        // 1) with z-buffer enabled, as the page curls, some of the forward facing triangles won't render at all if they are occluded by back facing 
        // triangles that were rendered first witha smaller z value
        // 2) with z-buffer disabled, a similar thing will happen, as the triangles render from top to bottom, the forward facing ones will render on top of 
        // the back facing ones from a previous row
        if (_curling)
        {
            for (int i = 0; i < 2; i++)
            {
                graphics->setCulling(i == 0 ? g::cull_state::counterClockwise : g::cull_state::clockwise);
                if (_mesh.get() != nullptr)
                {
                    if (this->getEffect().get() == nullptr)
                    {
                        _mesh->render(
                            graphics, 
                            _materials,
                            localToWorld, 
                            this->getDefaultEffect()
                            );
                    }
                    else
                    {
                        _mesh->renderWithEffect(
                            graphics, 
                            _materials,
                            localToWorld,
                            this->getEffect()
                            );
                    }
                }
            }
        }
        else // not curling
        {
            if (_mesh.get() != nullptr)
            {
                if (this->getEffect().get() == nullptr)
                {
                    _mesh->render(
                        graphics, 
                        _materials,
                        localToWorld, 
                        this->getDefaultEffect()
                        );
                }
                else
                {
                    _mesh->renderWithEffect(
                        graphics, 
                        _materials,
                        localToWorld,
                        this->getEffect()
                        );
                }
            }
        }

        if (pushedMatrix)
        {
            (void)graphics->popWorldMatrix();
        }
    }
    
    bool pixelHitTest(
        const std::shared_ptr<g::iscene_transformer>& sceneTransformer,
        const m::point2d& pixel,
        m::point3d* hitPositionLocalSpace
        )
    {
        // assume we won't intersect
        bool hitTestPassed = false;
        if (this->getIsVisible())
        {
            m::plane3d billboardPlane(m::point3d(0.0f,0.0f,0.0f), m::vector3d(0.0f,0.0f,1.0f));
            m::line3d worldLine;
            m::line3d localLine;

            float32 halfWidth = width()/2.0f;
            float32 halfHeight = height()/2.0f;
            float32 t;

            // convert pixel to a world space line segment (from near to back clipping planes)
            worldLine = sceneTransformer->pixelToWorldSpace(pixel);

            // transform into local space
            this->getWorldToLocal().apply(worldLine, &localLine);

            // if intersects, continue
            if (localLine.intersects(billboardPlane, &t))
            {
                m::point3d intersectionPoint;
                localLine.getPointAt(t, &intersectionPoint);

                // remember hit position in local space if param is non-nullptr
                if (hitPositionLocalSpace != nullptr)
                {
                    hitPositionLocalSpace->x = intersectionPoint.x;
                    hitPositionLocalSpace->y = intersectionPoint.y;
                    hitPositionLocalSpace->z = intersectionPoint.z;
                }

                // we hit if point is within our billboard width & height
                if (intersectionPoint.x >= -halfWidth && intersectionPoint.x <= halfWidth &&
                    intersectionPoint.y >= -halfHeight && intersectionPoint.y <= halfHeight)
                {
                    hitTestPassed = true;
                }
            }
        }

        return hitTestPassed;
    }

private:
    std::shared_ptr<g::imesh> _mesh;
    std::shared_ptr<g::imaterial_collection> _materials;

    bool _curling;
    b::uint32 _vertexCountX;
    b::uint32 _vertexCountY;
    b::float32 _offset;
    std::vector<g::vertex_lit_colored_textured> _vertexBuffer;
    std::vector<uint16> _indexBuffer;
    c::array_view<const c::index<1>, 2> *_ampIndexArray;
    c::array_view<b::float32, 2> *_ampVertexNormalArray;
    bool _ampInit;
};

}} // namespace journal::view
